﻿using Common.Logging;
using Quartz;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections;

namespace Hinacom.Scheduler
{
    /// <summary>
    /// 任务执行的处理程序基类。此类是对Quartz的IJob接口进行了一层封装并处理一些如记录Log，数据库记录执行历史等工作。
    /// </summary>
    public abstract class SchedulerJob : IJob
    {
        /// <summary>
        /// 生成错误日志。
        /// </summary>
        protected string GenerateErrorLog(Exception err)
        {
            StringBuilder log = new StringBuilder();
            Exception t = err;
            while (t != null)
            {
                log.AppendLine("-> " + t.Message);
                log.AppendLine(t.StackTrace);
                t = t.InnerException;
            }
            return log.ToString();
        }
        protected string StandardUtcTimeFormatForLog
        {
            get
            {
                return "'at 'HH':'mm':'ss' on 'MM'/'dd'/'yyyy' UTC'";
            }
        }

        protected bool FlushJobHistory(JobHistory history, ILog logger)
        {
            IJobStoreExt hisStore = null;

            try
            {
                hisStore = JobStoreExFactory.GetJobStoreExt();
            }
            catch(Exception ex)
            {
                logger.Error("Getting job store object failed. Saving execution history failed.", ex);
                return false;
            }


            if (hisStore == null)
            {
                logger.Info("The configured job store doesn't support the storage of execution history. The saving of history ignored.");
                return false;
            }

            try
            {
                hisStore.SaveJobHistory(history);
            }
            catch(Exception ex)
            {
                logger.Error("Saving job history failed.", ex);
                return false;
            }

            return true;
        }

        /// <summary>
        /// 执行任务。由IJob接口定义并由Quartz调用。
        /// </summary>
        /// <param name="context">Quartz定义的执行上下文。</param>
        void IJob.Execute(IJobExecutionContext context)
        {
            var logger = LogManager.GetLogger(GetType());
            logger.Info(String.Format("Executing Job: {0}, Group: {1}.", context.JobDetail.Key.Name, context.JobDetail.Key.Group));

            // 准备数据
            JobParameter parameter = new JobParameter();
            foreach (string key in context.JobDetail.JobDataMap.Keys)
                parameter[key] = context.JobDetail.JobDataMap[key];

            var exeContext = new SchedulerJobExecutionContext()
            {
                JobName = context.JobDetail.Key.Name,
                JobGroup = context.JobDetail.Key.Group,
                Parameter = parameter,
                ExecutionLog = new StringBuilder()
            };

            JobHistory history = new JobHistory()
            {
                JobName = context.JobDetail.Key.Name,
                JobGroup = context.JobDetail.Key.Group,
                StartTimeUtc = DateTime.UtcNow,
                Status = JobExecutionStatus.Running
            };

            FlushJobHistory(history, logger);

            // 执行任务
            try
            {
                exeContext.StartTimeUtc = DateTime.UtcNow;
                exeContext.ExecutionStarted = true;
                exeContext.ExecutionLog.AppendLine(String.Format("Job execution started {0}.", exeContext.StartTimeUtc.Value.ToString(StandardUtcTimeFormatForLog)));

                Execute(exeContext);

                exeContext.ExecutionSucceeded = true;

                history.Status = JobExecutionStatus.Succeeded;
            }
            catch (Exception ex)
            {
                exeContext.ExecutionError = ex;
                exeContext.ExecutionLog.AppendLine("The job execution terminated due to an error. Details:");
                exeContext.ExecutionLog.AppendLine(GenerateErrorLog(ex));

                history.Status = JobExecutionStatus.Failed;
            }
            finally
            {
                exeContext.CompleteTimeUtc = DateTime.UtcNow;
                exeContext.ExecutionCompleted = true;
                exeContext.ExecutionLog.AppendLine(String.Format("Job execution completed {0}.", exeContext.CompleteTimeUtc.Value.ToString(StandardUtcTimeFormatForLog)));
            }

            history.EndTimeUtc = DateTime.UtcNow;
            history.RunningLog = exeContext.ExecutionLog.ToString();

            FlushJobHistory(history, logger);

            logger.Info(String.Format("Execution completed. Job: {0}, Group: {1}.", context.JobDetail.Key.Name, context.JobDetail.Key.Group));
        }
        /// <summary>
        /// 由子类实现时，处理任务的执行流程。
        /// </summary>
        /// <param name="context">上下文数据。</param>
        protected abstract void Execute(SchedulerJobExecutionContext context);
    }

    public class SchedulerJobExecutionContext
    {
        private bool _executionSucceeded = false;
        /// <summary>
        /// 任务名称。
        /// </summary>
        public string JobName { get; set; }
        /// <summary>
        /// 任务从属的组。
        /// </summary>
        public string JobGroup { get; set; }
        /// <summary>
        /// 任务执行参数。
        /// </summary>
        public JobParameter Parameter { get; set; }
        /// <summary>
        /// 任务执行日志。该日志会被存储到数据库中。
        /// </summary>
        public StringBuilder ExecutionLog { get; set; }
        /// <summary>
        /// 任务执行是否已经启动。
        /// </summary>
        public bool ExecutionStarted { get; set; }
        /// <summary>
        /// 任务执行是否已经完成。
        /// </summary>
        public bool ExecutionCompleted { get; set; }
        /// <summary>
        /// 任务执行是否成功。在任务执行完成之前，此值始终为false。
        /// </summary>
        public bool ExecutionSucceeded
        {
            get { return ExecutionCompleted && _executionSucceeded; }
            set { _executionSucceeded = value; }
        }
        /// <summary>
        /// 执行时发生的导致整个执行失败的错误。对于任务实现自己捕获并处理的错误，应由实现自己记入日志。
        /// </summary>
        public Exception ExecutionError { get; set; }
        /// <summary>
        /// 执行开始的UTC时间。
        /// </summary>
        public DateTime? StartTimeUtc { get; set; }
        /// <summary>
        /// 执行结束的UTC时间。
        /// </summary>
        public DateTime? CompleteTimeUtc { get; set; }
    }

}
